package com.sp.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sp.exception.BusinessException;
import com.sp.mapper.BillIncompleteMapper;
import com.sp.mapper.UserMapper;
import com.sp.model.dto.UserDTO;
import com.sp.model.entity.BillIncomplete;
import com.sp.model.entity.RedisData;
import com.sp.model.entity.User;
import com.sp.service.UserService;
import com.sp.utils.EmployeeThreadLocal;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static com.sp.constant.BillMessageConstant.PARAM_ERROR;
import static com.sp.constant.RedisConstant.*;
import static com.sp.constant.UserMessageConstant.*;

/**
 * @author tong
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    private static final ExecutorService EXECUTOR =
            new ThreadPoolExecutor(8, 8, 0, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(20));

    @Autowired
    private BillIncompleteMapper billIncompleteMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Transactional
    @Override
    public boolean addUser(UserDTO userDTO) {
        if (userDTO == null || StrUtil.isBlank(userDTO.getName()) || StrUtil.isBlank(userDTO.getCarNumber())
                || StrUtil.isBlank(userDTO.getPhone())) {
            throw new BusinessException(ADD_USER_FAIL + "：" + PARAM_ERROR);
        }

        User user = new User();
        BeanUtils.copyProperties(userDTO, user);

        // 查询数据库中是否有该车牌相关用户（未删除、删除）
        User oldUser = this.baseMapper.selectByCarNumberContainDel(userDTO.getCarNumber());
        if (oldUser != null) {
            if (oldUser.getDeleted() == 0) {
                throw new BusinessException(ADD_USER_FAIL + "：该车牌号已经存在用户");
            } else {
                // 更新旧数据
                user.setId(oldUser.getId());
                user.setDeleted(0);
                user.setCreateTime(LocalDateTime.now());
                user.setCreateEmployee(EmployeeThreadLocal.getEmployee().getId());
                return this.baseMapper.updateDeletedById(user);
            }
        } else {
            // 数据库中不存在与该车牌相关用户
            return this.save(user);
        }
    }

    @Transactional
    @Override
    public boolean deleteUsers(List<Integer> ids) {
        if (ids == null || ids.size() == 0) {
            throw new BusinessException(DELETE_USER_FAIL + "：" + PARAM_ERROR);
        }

        // 查询是否有未完成的账单
        for (Integer id : ids) {
            BillIncomplete billIncomplete = billIncompleteMapper.selectByUserId(id);
            if (billIncomplete != null) {
                throw new BusinessException(DELETE_USER_FAIL + "：" + billIncomplete.getName() + "用户存在未完成的账单不能删除");
            }
        }

        return this.removeByIds(ids);
    }

    @Transactional
    @Override
    public boolean updateUser(UserDTO userDTO) {
        if (userDTO == null || userDTO.getId() == null) {
            throw new BusinessException(UPDATE_USER_FAIL + "：" + PARAM_ERROR);
        }

        User user = new User();
        BeanUtils.copyProperties(userDTO, user);

        return this.updateById(user);
    }

    @Override
    public User selectById(Integer id) {
        if (id == null) {
            throw new BusinessException(SELECT_USER_FAIL + "：" + PARAM_ERROR);
        }
        return this.getById(id);
    }

    @Override
    public IPage<User> page(Integer currentPage, Integer pageSize, UserDTO userDTO) {
        if (currentPage == null) {
            currentPage = 1;
        }

        if (pageSize == null) {
            pageSize = 10;
        }

        //条件查询
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        if (userDTO != null) {
            queryWrapper.like(StrUtil.isNotBlank(userDTO.getName()), "`u`.`name`", userDTO.getName())
                    .eq(StrUtil.isNotBlank(userDTO.getSex()), "`u`.`sex`", userDTO.getSex())
                    .like(StrUtil.isNotBlank(userDTO.getPhone()), "`u`.`phone`", userDTO.getPhone())
                    .like(StrUtil.isNotBlank(userDTO.getCarNumber()), "`u`.`car_number`", userDTO.getCarNumber())
                    .gt(null != userDTO.getStartTime(), "`u`.`create_time`", userDTO.getStartTime())
                    .lt(null != userDTO.getEndTime(), "`u`.`update_time`", userDTO.getEndTime());
        }
        queryWrapper.eq("`u`.`deleted`", 0);
        IPage<User> page = new Page<>(currentPage, pageSize);
        return this.baseMapper.page(page, queryWrapper);
    }

    @Override
    public User selectByCarNumber(String carNumber) {
        if (StrUtil.isBlank(carNumber)) {
            throw new BusinessException(SELECT_USER_FAIL + "：" + PARAM_ERROR);
        }

        String json = stringRedisTemplate.opsForValue().get(USER_INFO_CARNUMBER + carNumber);

        if (json == null) {
            //该键不存在，查询数据库
            User user = baseMapper.selectByCarNumber(carNumber);
            if (user == null) {
                // 不存在此用户，存入""，防止缓存穿透
                stringRedisTemplate.opsForValue().set(USER_INFO_CARNUMBER + carNumber, "", USER_INFO_NULL_TTL, TimeUnit.MINUTES);
            } else {
                // 存在，设置逻辑过期字段，防止缓存雪崩
                RedisData<User> redisData = new RedisData<>(LocalDateTime.now().plusMinutes(USER_INFO_TTL), user);
                stringRedisTemplate.opsForValue().set(USER_INFO_CARNUMBER + carNumber, JSONObject.toJSONString(redisData));
                return user;
            }
        }

        // 该键存在，非空字符串
        if (StrUtil.isNotBlank(json)) {
            RedisData<User> redisData = JSONObject.parseObject(json, new TypeReference<RedisData<User>>() {
                /*  匿名内部类，继承关系
                                同一个类中	同一个包中	不同包的子类	不同包的无关类
                    public	        ✔	        ✔	        ✔	        ✔
                    protected	    ✔	        ✔	        ✔
                    无（空着不写）     ✔	        ✔
                    private	        ✔
                */
            });

            assert redisData != null;
            if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {
                // 缓存过期，进行缓存重建
                String lockKey = USER_LOCK_CARNUMBER + carNumber;
                Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, carNumber,
                        USER_LOCK_CARNUMBER_TTL, TimeUnit.MINUTES);

                // 获取锁成功的线程，新建线程进行缓存重建
                if (Boolean.TRUE.equals(lock)) {
                    EXECUTOR.submit(() -> {
                        User user = baseMapper.selectByCarNumber(carNumber);
                        if (user == null) {
                            stringRedisTemplate.opsForValue().set(USER_INFO_CARNUMBER + carNumber, "", USER_INFO_NULL_TTL, TimeUnit.MINUTES);
                        } else {
                            stringRedisTemplate.opsForValue().set(USER_INFO_CARNUMBER + carNumber,
                                    JSONObject.toJSONString(new RedisData<>(LocalDateTime.now().plusMinutes(USER_INFO_TTL), user)));
                        }

                        // 重建完毕，释放锁
                        stringRedisTemplate.delete(lockKey);
                    });
                }
            }

            // 1. 缓存未过期，直接返回
            // 2. 未获取到锁，会出现短暂数据不一致。但是应该车牌号不会出现问题，找不到车就进不来
            return redisData.getData();
        }

        // 值为""，直接返回
        return null;
    }

}
